package JavaEE.ManyThread8;
import java.rmi.server.ExportException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/** 乐观锁 vs 悲观锁 **/
// 使用锁的时候,会预测这个锁遇到冲突的概率:
// 乐观锁:预测锁冲突的概率比较高. -> 更倾向于在锁中做一些"阻塞"操作
// 悲观锁:预测锁冲突的概率比较低. -> 认为锁冲突概率不高,尝试用其他方式代替阻塞(忙等,版本号,失败重试...)

/** 重量级锁 vs 轻量级锁 **/
// 重量级锁:加锁的开销比较大,等待锁的线程,等待时间可能比较长
// 轻量级锁:加锁的开销比较小,等待锁的线程,等待时间相对较短.

/** 等待挂起锁 vs 自旋锁 **/
// 挂起等待锁:"悲观锁"/"重量级锁"的典型实现,遇到锁冲突,调度出cpu,等待cpu的唤醒.
// 自旋锁:"乐观锁"/"轻量级锁"的典型实现,遇到锁冲突,不会放弃cpu,而是采取"忙等"不断尝试获取锁.
// (比如追女神失败)
// 挂起等待锁:和女神不联系,某天想起再看
// "追女神的人多,机会不大,适合这样做."
// (优点:女神单身时,不能及时知道)
// (缺点:有精力去做别的事情了)
// 自旋锁:死缠烂打
// "追女神的人少,更有机会的时候,这样更有性价比.相反,如果锁竞争激烈,都自旋,则消耗cpu过多,不划算"
// (优点:女神单身时,立马就知道)
// (缺点:没有精力去做别的事情)

/** 公平锁 vs 非公平锁 **/
// 公平锁:先来后到
// 非公平锁:概率均等

/** 读写锁 vs 互斥锁 **/
// 互斥锁:synchronized
// 读写锁: 加锁分成"加读锁"和"加写锁" -> (读多写少,使"读锁"和"读锁"间不互斥)

/** synchronized 实现原理 **/
// 1.锁升级 (无锁 -> 偏向锁 -> 自旋锁 -> 重量级锁) (锁升级不可逆)
// 偏向锁:只标记线程id,不真的锁,等到有其他线程要竞争,才真的锁
// 2.锁消除 ("有些代码写了加锁,但JVM执行时发现没必要,则会自动去除锁")
// 3.锁粗化 (消除相邻的,多次出现,并且效果类似的加锁解锁操作,将它们合并成同一个锁,可以提高效率和减少开销)
// 比如:"老板给你安排了3个任务,你做完任务给老板汇报了1个任务,挂断了电话.然后想起还有第2个,又汇报.再想起第3个,又汇报..."
// 以上的做法是不合理的,并且消耗老板和自身的时间也都更多,所以应该只打一个电话(锁粗化).

/** CAS是什么? **/
// CAS(Compare-And-Swap):具有"原子性"的swap操作,效率高并且适用于多线程.用于"比较某个内存地址的当前值是否与预期值相同"
// 如果相同,则认为该数据没有被其他线程修改过,则可以进行这次对数据的修改.
// 如果不相同,认为被其他线程修改过,则通过"自旋"的方式或者"其他策略"处理
// (原子类就是以CAS实现,能够更快的进行++,--等操纵)

/** CAS的底层原理 **/
// 其内部原理是:使该线程的寄存器数据与内存数据进行比较
// 三步操作:
// 1.读取内存中的当前值V
// 2.比较V与预期值A
// 3.如果V==A,则将新值(B)写入内存,否则放弃操作

// CAS的伪代码
// public int CAS(int V,int A,int new_val){
//     int old_val = V;
//     if(old_val == A){
//         old_val = new_val;
//     }
//     return old_val;
// }

/** synchronized 和 ReentrantLock **/
// 1.实现的区别
// synchronized 是一个"关键字",在JVM内部实现(大概率C++)
// ReentrantLock 是标准库的一个类,在JVM外实现(基于Java实现)

// 2.释放锁的区别(synchronized的优点)
// synchronized 使用时"不需要"手动释放锁
// ReentrantLock 使用时"需要"手动释放锁

// 3.申请锁失败的处理方法(ReentrantLock的优点)
// synchronized 申请锁失败时,只能"死等"
// ReentrantLock 申请锁失败时
// 可以使用unlock()"死等",或者trylock()"尝试" -> "等待一段时间就放弃"

// 4.公平锁与非公平锁的区别(ReentrantLock的优点)
// synchronized是非公平锁
// ReentrantLock默认是非公平锁,可以通过构造方法传入一个true变成公平锁

// 5.唤醒机制(ReentrantLock的优点)
// synchronized是通过Object的(wait/notify)实现"等待-唤醒".每次唤醒一个随即等待线程
// ReentrantLock是搭配Condition类实现"等待-唤醒",可以更精确的控制唤醒某个指定的线程



/** 通过callable创建线程 **/
// 作用:使用lambda创建线程时,runnable是没有返回值的,
// 想要获取到某个值,必须在线程外创建一个接收的值.
// 但这种做法,就会导致"主线程和t线程"的耦合度变高.
// 而"callable创建线程"就能避免这种情况.
public class Demo1 {
    // 例如:使用一个线程获取1~100的和
    public static int sum = 0;
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Thread t1 = new Thread(() -> {
            int num = 0;
            for(int i = 1;i <= 100;i++){
                num += i;
            }
            // 线程t1外部的变量
            sum = num;
        });
        t1.start();
        t1.join();
        System.out.println(sum);
        // 然而,我们可以通过callable来创建一个具有返回值的线程:
        // Integer就是返回值类型
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int n = 0;
                for(int i = 1;i <= 100;i++){
                    n += i;
                }
                return n;
            }
        };
        // 但是callable不像runnable,能够直接用于创建线程,
        // 而是需要一个"借助的类":FutureTask (类似于只能用对应的小票才能取餐,不在runnable中实现还是为了降低耦合)
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t2 = new Thread(futureTask);
        t2.start();
        // 获取和
        sum = futureTask.get();
        t2.join();
        System.out.println(sum);
    }
}


